/**
* HiloDeCliente
* 
* Se encarga de la atencion de cada uno de los clientes
* que se conecten al servidor. Cada uno de los clientes
* corre en un hilo separado.
*/

import java.io.*;
import java.util.*;
import java.util.logging.Logger;
import java.net.Socket;
import java.lang.Thread;

public class HiloDeCliente  implements Runnable{
    
   Socket socketCliente;
   String ipCliente;
   String loginUsuario;
//   static boolean salir = false;
   
   static Hashtable<String, Queue<String>> tablaIdColaMensajes = new Hashtable<String, Queue<String>>();
   
   /*
    * Metodo constructor de la clase
    */
   public HiloDeCliente(String ip, Socket client){
        this.socketCliente = client;
        this.ipCliente = ip;
   }
   
   void insertarEnTabla(String id){
	   	//Si el cliente actual no esta en la tabla se agrega con una cola vacia
		if (!tablaIdColaMensajes.containsKey(id)){
 	 		Queue <String> colaInicial = new LinkedList<String>();
      		tablaIdColaMensajes.put(id, colaInicial);
      		System.out.println("HDC: tablaIdColaMensajes luego de insertarEnTabla: " 
      				+ tablaIdColaMensajes);
  		};
   }
   
   private boolean distance(double lat1, double lon1, double lat2, double lon2, String unit) {
	   double distMax = 500.0;
	   boolean estaCerca;
	   double theta = lon1 - lon2;
	   double dist = Math.sin(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(deg2rad(theta));
	   dist = Math.acos(dist);
	   dist = rad2deg(dist);
	   dist = dist * 60 * 1.1515;
	   if (unit.equals("K")) {
	     dist = dist * 1.609344;
	   } else if (unit.equals("N")) {
	     dist = dist * 0.8684;
	     }
	   if (dist > distMax){
		   estaCerca = false;
	   }else{
		   estaCerca = true;
	   }
	   return estaCerca;
	 }

	 private double deg2rad(double deg) {
	   return (deg * Math.PI / 180.0);
	 }

	 private double rad2deg(double rad) {
	   return (rad * 180.0 / Math.PI);
	 }

    public void run(){
         try {
        	 //Lo primero que envia el usuario es su login
        	 BufferedReader reciboLogin = new BufferedReader(new InputStreamReader(socketCliente.getInputStream()));
        	 loginUsuario =  reciboLogin.readLine();
//        	 String loginLatLong =  reciboLogin.readLine();
//        	 System.out.println("Primer mensaje leido: " + loginLatLong);
//        	 String [] arrayLoginLatLong = loginLatLong.split("[|]");
//        	 loginUsuario = arrayLoginLatLong[0];
//        	 Server.tablaGPS.put(loginUsuario,arrayLoginLatLong[1]);
//        	 Server.tablaGPS.put("guille", "32.9697%-96.80322");
        	 System.out.println("HDC: Nuevo cliente: " + loginUsuario + " con ip: " + ipCliente);
        	 Server.tablaConectados.put(loginUsuario,1);
        	 System.out.println("HDC: Tamano del hash tablaConectados: " + Server.tablaConectados.size() + ":" + Server.tablaConectados);
             //Si el cliente actual no esta en la tabla se agrega con una cola vacia
             insertarEnTabla(loginUsuario);
        	 
        	 /*
        	  * Creo un hilo que se encargara de enviarle al cliente los mensajes
        	  * que esten encolados en tablaIdColaMensajes que sean suyos
        	  */
        	 Runnable escritura = new HiloDeClienteEscritura(this.loginUsuario, this.socketCliente);
 		     Thread hiloEscritura = new Thread(escritura);
 		     hiloEscritura.start();

 		     BufferedReader in = new BufferedReader(new InputStreamReader(socketCliente.getInputStream()));
 		     PrintWriter enviarListaAmigos = new PrintWriter(new BufferedWriter(
      			  new OutputStreamWriter(socketCliente.getOutputStream())), true);
 		     String str;
 		     //Leo del socket
 		     while((str = in.readLine()) != null){ 
            	 System.out.println("________________________________________________________" +
            	 		"\nHDC: Recibiendo de " + loginUsuario + " desde la ip: " + ipCliente);          	 
                 try {
				      //A quien va enviado el mensaje y el respectivo mensaje 
				      String idDestino = "";
				      String mensaje = "";
				      /*
				      tokens es el arreglo que contiene los campos que seran procesados luego.
				      Depende el formato, de cada tipo de mensaje recibido.
				      */
					  String[] tokens = str.split("[|]");
					  
					  /*
					  idDestino es la cabecera del mensaje. Puede ser:
					  Broadcast, online, offline, usuario_destino
					  */
					  idDestino = tokens[0].trim();
					  mensaje = tokens[1].trim();
					  
					  /*
					  Inserto en tablaIdColaMensajes el usuario al que le voy a
					  escribir de no existir
					  */
					  if (!idDestino.equals("Broadcast") & !idDestino.equals("AmigosOnline")
							 & !idDestino.equals("GPS") & !idDestino.equals("actualizarGPS")
							 & !idDestino.equals("AmigosOffline") & !idDestino.equals("Salir")){
						  insertarEnTabla(idDestino);
					  }
			             
		              if(idDestino.equals("Broadcast")){
					      //llego un mensaje de tipo [Broadcast|mensaje|amigo1$amigo2]
		            	  System.out.println("HDC: IF Broadcast");
		            	  String [] listaBroadcast = tokens[2].split("[$]");
		            	  System.out.println(">>>>HDC: listaBroadcast: " + Arrays.toString(listaBroadcast));
		            	  for (int i=0; i < listaBroadcast.length; i++){
		            		  System.out.println("\n>>>>HDC: iteracion actual usuario: " + listaBroadcast[i]);
		            		  insertarEnTabla(listaBroadcast[i]);
		            		  tablaIdColaMensajes.get(listaBroadcast[i]).add(loginUsuario + "$" + mensaje);
		            		  System.out.println(">>>>HDC: Se añadio el mensaje: " + mensaje + " en la cola de: " 
		            				  + listaBroadcast[i]);
		            	  }
		            	  System.out.println("\nHDC: cola de mensajes despues del broad: " + tablaIdColaMensajes);
		              }else if(idDestino.equals("AmigosOnline")){
			              //llego un mensaje de tipo [AmigosOnline|lista_de_amigos]
		            	  String recibido = in.readLine();
		            	  System.out.println("HDC: IF AmigosOnline" + recibido);
		            	  String stringUsuariosReplaced = recibido.replace("[", "").replace("]", "").replace("\"", "");
		            	  String [] misAmigos = stringUsuariosReplaced.split(",");
		            	  ArrayList <String> listaAmigosConectados = new ArrayList<String>();
		            	  
		            	  for (String unAmigo : misAmigos) {
		            		  try{
			            	      if (Server.tablaConectados.get(unAmigo.trim()) == 1){
			            	    	  listaAmigosConectados.add(unAmigo.trim());
			            	      }
		            		  }catch (Exception e){
//		            			  System.out.println("No encontre a: " + unAmigo);            
		            		  }
		            	  }
		            	  //Le agrego en la posicion 0 [Enviar mensaje a todos]
		            	  listaAmigosConectados.add(0, misAmigos[0]);
//		            	  PrintWriter enviarListaAmigos = new PrintWriter(new BufferedWriter(
//		            			  new OutputStreamWriter(socketCliente.getOutputStream())), true);
		            	  enviarListaAmigos.println("AmigosOnline");
		            	  enviarListaAmigos.println(listaAmigosConectados.toString());
		            	  System.out.println("HDC: Envie listaAmigosConectados: " + listaAmigosConectados 
		            			  + " a " + loginUsuario);
		              }else if(idDestino.equals("AmigosOffline")){
			              //llego un mensaje de tipo [AmigosOffline|lista_de_amigos]
		            	  String recibido = in.readLine();
		            	  System.out.println("HDC: IF AmigosOffline" + recibido);
		            	  String stringUsuariosReplaced = recibido.replace("[", "").replace("]", "").replace("\"", "");
		            	  String [] misAmigos = stringUsuariosReplaced.split(",");
		            	  ArrayList <String> listaAmigosDesconectados = new ArrayList<String>();
		            	  
		            	  for (String unAmigo : misAmigos) {
		            		  try{
		            	      if (Server.tablaConectados.get(unAmigo.trim()) == 0){
		            	    	  listaAmigosDesconectados.add(unAmigo.trim());
		            	      }
		            		  }catch (Exception e){
		            			  //Si en la tabla no existe el usuario que busco
		            			  //hay una excepcion y agrego a ese posible amigo
		            			  listaAmigosDesconectados.add(unAmigo.trim());            
		            		  }
		            	  }
//		            	  PrintWriter enviarListaAmigos = new PrintWriter(new BufferedWriter(
//		            			  new OutputStreamWriter(socketCliente.getOutputStream())), true);
		            	  enviarListaAmigos.println("AmigosOffline");
		            	  enviarListaAmigos.println(listaAmigosDesconectados.toString());
		            	  System.out.println("HDC: Envie listaAmigosDesconectados: " + listaAmigosDesconectados 
		            			  + " a " + loginUsuario);
		              }else if(idDestino.equals("actualizarGPS")){
		            	  Server.tablaGPS.put(loginUsuario, mensaje);
		            	  System.out.println("HDC: Se recibio actualizacion GPS de: " + loginUsuario);
		              }else if(idDestino.equals("GPS")){
		            	  System.out.println("HDC: IF AmigosGPS");
			              // llego un mensaje de tipo GPS|x y luego lista_de_amigos|miPosicion
			              // recibido tiene un formato lista_de_amigos|miPosicion siendo miPosicion = latitude$longitud
		            	  String recibido = in.readLine();
		            	  String amigosRecibidos = recibido.split("[|]")[0];
		            	  String miPosicion = recibido.split("[|]")[1];
		            	  double miLatitud = Double.parseDouble(miPosicion.split("[%]")[0]);
		            	  double miLongitud = Double.parseDouble(miPosicion.split("[%]")[1]);
		            	  System.out.println("HDC: IF AmigosOnline" + amigosRecibidos);
		            	  String stringUsuariosReplaced = amigosRecibidos.replace("[", "").replace("]", "").replace("\"", "");
		            	  String [] misAmigos = stringUsuariosReplaced.split(",");
		            	  ArrayList <String> amigosConectadosCercanos = new ArrayList<String>();
		            	  
		            	  //TODO enviar a todos los usuarios conectados una solicitud 
		            	  //para que me manden su ubicacion actualizada
//		            	  Set <String> set = Server.tablaUsuarioSocket.keySet();
////		                  String siguienteIp = null;
//		                  Iterator <String> itr = set.iterator();
//		                  while (itr.hasNext()) {
//		                      Socket socketItr = Server.tablaUsuarioSocket.get(itr.next());
//		                      try {
//		                          PrintWriter out = new PrintWriter(new BufferedWriter
//		                              (new OutputStreamWriter(socketItr.getOutputStream())), true);
//		                          out.println("actualizarGPS");
//		                      } catch (IOException ex) {
//		                      }
//		                  }
		            	  
		            	  for (String unAmigo : misAmigos) {
		            		  try{
		            			  String suPosicion = Server.tablaGPS.get(unAmigo);
		            			  double suLatitud = Double.parseDouble(suPosicion.split("[%]")[0]);
				            	  double suLongitud = Double.parseDouble(suPosicion.split("[%]")[1]);
		            			  boolean estaCerca = distance(miLatitud, miLongitud, suLatitud, suLongitud, "K");
		            	      if (Server.tablaConectados.get(unAmigo.trim()) == 1 && estaCerca){//&& estaCerca
		            	    	  amigosConectadosCercanos.add(unAmigo.trim());
		            	      }
		            		  }catch (Exception e){
//		            			  System.out.println("No encontre a: " + unAmigo);            
		            		  }
		            	  }
		            	  //Le agrego en la posicion 0 [Enviar mensaje a todos]
//		            	  System.out.println("#####GPS: " + amigosConectadosCercanos.toString());
		            	  amigosConectadosCercanos.add(0, misAmigos[0]);
		            	  enviarListaAmigos.println("AmigosGPS");
		            	  enviarListaAmigos.println(amigosConectadosCercanos.toString());
		            	  System.out.println("HDC: Envie listaAmigosGPS: " + amigosConectadosCercanos 
		            			  + " a " + loginUsuario);
		              }else if(idDestino.equals("Salir")){
						    //Mensaje de cierre de conexion
							System.out.println("HDC: El cliente solicito cerrar su conexion.");
//							socketCliente.close();
							break;
                 	  }else{
			              //llego un mensaje de tipo [idDestino|mensaje]
		            	  System.out.println("HDC: IF ip|Mensaje");
		            	  try{
		            		  //Se anade el mensaje a la cola del usuario destino.
		            		  tablaIdColaMensajes.get(idDestino).add(loginUsuario + "$" + mensaje);
		            		  System.out.println("HDC: Cola de mensajes actuales de " + idDestino +": "
		            				  + tablaIdColaMensajes.get(idDestino));
		            	  }catch(Exception e){
		            		  System.out.println("HDC: Error en la cola del usuario " + loginUsuario 
		            				  + ":" + ipCliente);
					    	}
				      }
                 }catch(Exception e){
//                	 System.out.println("HDC: Error");
//                   System.out.println("Estado Socket: " + socketCliente.isClosed());
                     e.printStackTrace();
//                     reciboLogin.close();
//                     in.close();
//                     socketCliente.close();
                 }
             }//while principal
             //Se ha cerrado el socket del cliente.
             System.out.println("\n_____HDC: Fin de la conexion de lectura con " + loginUsuario + ":" 
            		 + ipCliente + "_____");
             in.close();
             enviarListaAmigos.close();
             socketCliente.close();
         }catch (Exception e){
             System.out.println("HDC: Error HiloDeCliente");
             e.printStackTrace();
         }finally{
        	 Server.tablaConectados.put(loginUsuario,0);
         }
    }
}